<?php

class Catalogue_Model_Object extends Catalogue_Model_Object_Abstract {

	/**
	 * При выборке списка категорий объекта сортировать по приоритету категории
	 * относительно объекта (поле priority)
	 */
	const SORT_BY_CATEGORY_PRIORITY = 1;

	/**
	 * При выборке списка категорий объекта сортировать по индексу сортировки категории
	 */
	const SORT_BY_CATEGORY = 2;

	/**
	 * При выборке списка категорий объекта сортировать по индексу сортировки категории
	 * и по приоритету категории относительно объекта.
	 *
	 * Сортировать вначале по индексу сортировки категории.
	 */
	const SORT_BY_CATEGORY_FIRST = 3;

	/**
	 * При выборке списка категорий объекта сортировать по приоритету категории относительно объекта
	 * и по индексу сортировки категории.
	 *
	 * Сортировать вначале по приоритету категории относительно объекта.
	 */
	const SORT_BY_CATEGORY_PRIORITY_FIRST = 4;

    /**
     * Тип объекта
     * @var string
     */
    protected $_objectType = Catalogue_Model_Object_Abstract::T_OBJECT;
    
    /**
     * Содержит связи
     * @var bool
     *
     */
    protected $_hasRelations = true;

    /**
     * Поля уровня приложения
     * @var array
     *
     */
    protected $_appData = array(
        'location' => array(),
        'label'     => array(),
        'display' => null
    );

    protected $_attrValues = null;

    protected $_attrValuesEnum = null;

    /**
     * Список категорий на добавление
     * @var array
     *
     */
	protected $_categories = array();

    /**
     * Список категорий на удаление
     * @var array
     *
     */
    protected $_categoriesToDelete = array();

    /**
     * Список связей на добавление
     * @var array
     *
     */
    protected $_objects = array();

    /**
     * Список связей на удаление
     * @var array
     *
     */
    protected $_objectsToDelete = array();

    /**
     * Список значений атрибутов на добавление
     * @var array
     *
     */
    protected $_attrs = array();

    /**
     * список значений атрибутов на удаление
     * @var array
     *
     */
	protected $_attrsToDelete = array();

    /**
     * Файлы на добавление
     *
     * @var array
     *
     */
	protected $_files = array();

    /**
     * Возвращает объект Catalogue_Model_Object по primary key
     *
     * @static
     * @param  mixed $identity
     * @return Catalogue_Model_Object
     *
     */
	static function getInstance($identity) {
		$storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
		return self::getFromStorage($storage, $identity);
	}

    /**
     * Создает новый объект Catalogue_Model_Object с полями $data
     *
     * @static
     * @param  array $data
     * @return Catalogue_Model_Object
     *
     */
	static function create($data) {
		$storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
		return $storage->create(self::getInputData($data));
	}

    /**
     * Возвращает список объектов
     *
     * @static
     * @param  mixed $where
     * @param  mixed $order
     * @param  int $limit
     * @param  int $offset
     * @return Catalogue_Model_Object_Iterator
     *
     */
    static function getAll($where = null, $order = null, $limit = null, $offset = null, $offsetByPage = false) {
        $storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
        return $storage->getAll($where, $order, $limit, $offset, $offsetByPage);
    }

    /**
     * Возвращает список объектов из указанной категории
     *
     * @static
     * @param  $category
     * @param  $where
     * @param  $order
     * @param  $limit
     * @param  $offset
     * @return Catalogue_Model_Object_Iterator
     */
	static function getAllByCategory($category, $where = null, $order = null, $limit = null, $offset = null) {
		if (!$category instanceof Catalogue_Model_Category) {
			$category = Catalogue_Model_Category::getInstance($category);
		}

        $offset = self::_getSqlOffset($limit, $offset);

		$storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
        return $storage->getAllByCategory($category, $where, $order, $limit, $offset);
	}
	
    /**
     * Возвращает список объектов из указанных категорий
     *
     * @static
     * @param  array $categories массив id связаных категорий
     * @param  $where
     * @param  $order
     * @param  $limit
     * @param  $offset
     * @return Catalogue_Model_Object_Iterator
     */
	static function getAllByCategories($categories, $where = null, $order = null, $limit = null, $offset = null, $offsetByPage = false) {
		$storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
        return $storage->getAllByCategories( $categories, $where, $order, $limit, $offset, $offsetByPage);
	}

    /**
     * Возвращает список объектов по символьному коду (поле code)
     * 
     * @static
     * @param  $code
     * @return Catalogue_Model_Object_Iterator
     */
    static function getByCode($code) {
        $storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
        return $storage->getAll(array('code = ?' => $code));
    }

    function __sleep() {
        $this->_clean();
        parent::__sleep();
    }

    function __clone() {
        if (null === $this->_storage) {
            $storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
            $this->_storage = $storage;
        }
    }

    /**
     * Устанавливает новые значения полей объекта
     *
     * @param  array $data
     * @return Catalogue_Model_Object_Abstract
     */
    function setData($data) {
        return parent::setData(self::getInputData($data));
    }

    /**
     * Сохраняет объект (добавляет в хранилище новый созданный объект или обновляет существующий)
     *
     * @return string
     *
     */
	function save() {

		$this->_initStorage();

		//сохранение в базе нового объекта
        if (!$this->isStored()) {
			//обнулим статистику связей
			$this->_data['attributes_s'] 	= 0;
			$this->_data['attributes_l'] 	= 0;
			$this->_data['categories'] 		= 0;
			$this->_data['objects'] 		= 0;
			$this->_data['files'] 			= 0;

			parent::save();

            if ($this->_data['category_id'] > 0) {
                $this->addCategory($this->_data['category_id']);
            }
        } else {
			$this->setIsModified(true);
		}

        $result = true;

		//удаление значений атрибутов
		if ($this->_attrsToDelete) {
			foreach ($this->_attrsToDelete as $params) {
				$attr = $params['attr'];
                $value = $params['value'];
				$result = $this->_deleteAttrValue($attr, $value);
				if (!$result) {
                    throw new Workset_Model_Exception("Delete attribute error, id:{$this->getId()}, attr_id:{$attr->getId()}");
                } 
			}
		}

		//добавление значений атрибутов
        if ($this->_attrs) {
            foreach ($this->_attrs as $params) {
                $attr = $params['attr'];
                $value = $params['value'];

                if ($attr->isEnum()) {
                    $result = $this->_setAttrValueEnum($attr, $value);
                } else {
                    $result = $this->_setAttrValue($attr, $value, $valueId);
                }

                if (!$result) {
                    throw new Workset_Model_Exception("Add attribute error, id:{$this->getId()}, attr_id:{$attr->getId()}");
                }
            }
        }

		//удаление связанных категорий
        if ($this->_categoriesToDelete) {
            foreach ($this->_categoriesToDelete as $category) {
                $this->unlinkObject($category);
            }
        }

		//добавление связанных категорий
        if ($this->_categories) {
            foreach ($this->_categories as $category) {
                $this->linkToObject($category);
            }
        }

		//удаление связанных объектов
        if ($this->_objectsToDelete) {
            foreach ($this->_objectsToDelete as $object) {
                $this->unlinkObject($object);
            }
        }

		//добавление связанных объектов
        if ($this->_objects) {
            foreach ($this->_objects as $object) {
                $this->linkToObject($object);
            }
        }

		//очистка очереди
        $this->_clean();

		//необходимо обновление полей объекта в базе
        if ($this->isModified()) {
            parent::save();
        }

		return $this->getId();
	}

    /**
     * Удаляет объект из хранилища
     *
     * @return string
     *
     */
	function delete() {
        //удаляем файлы
        if (!$this->dropFiles()) {
            throw new Workset_Model_Exception("Can't delete object files");
        }

		//уменьшаем счетчик объектов у категории
        foreach ($this->getCategories() as $category) {
            $category->decrementObjectsCount()->save();
        }

		$this->_initStorage(Catalogue_Model_Object_Abstract::T_OBJECT);

		return parent::delete();
	}

    /**
     * Приводит объект к первоначальному состоянию,
     * в котором объект находился при создании или при получении из базы
     * 
     * @return void
     */
    function reset() {
        $this->_clean();
    }

    /**
     * Создает связь между объектами
     *
     * @param Workset_Model_Object_Abstract $object
     * @return bool
     *
     */
    function linkToObject(Workset_Model_Object_Abstract $object, $data = null) {
        $res = parent::linkToObject($object, $data);
		if ($res) {
			if ($object instanceof Catalogue_Model_Category) {
				$this->incrementCategoriesCount();
			} else {
				$this->incrementObjectsCount();
			}
		}
		return $res;
    }

    /**
     * Удаляет связь между объектами
     *
     * @param Workset_Model_Object_Abstract $object
     * @return bool
     *
     */
    function unlinkObject(Workset_Model_Object_Abstract $object) {
        $res = parent::unlinkObject($object);
		if ($res) {
			if ($object instanceof Catalogue_Model_Category) {
				$this->decrementCategoriesCount();
			} else {
				$this->decrementObjectsCount();
			}
		}
		return $res;
    }

    /**
     * Увеличивает счетчик связанных категорий
     *
     * @return Catalogue_Model_Object
     */
	function incrementCategoriesCount() {
		$this->_data['categories'] = $this->_data['categories'] + 1;
		$this->_update = true;
        return $this;
	}

    /**
     * Уменьшает счетчик связанных категорий
     *
     * @return Catalogue_Model_Object
     */
	function decrementCategoriesCount() {
		if ($this->_data['categories'] > 0) {
            $this->_data['categories'] = $this->_data['categories'] - 1;
			$this->_update = true;
        }
        return $this;
	}

    /**
     * Увеличивает счетчик связанных объектов
     *
     * @return Catalogue_Model_Object
     */
	function incrementObjectsCount() {
		$this->_data['objects'] = $this->_data['objects'] + 1;
		$this->_update = true;
        return $this;
	}

    /**
     * Уменьшает счетчик связанных объектов
     *
     * @return Catalogue_Model_Object
     */
	function decrementObjectsCount() {
		if ($this->_data['objects'] > 0) {
            $this->_data['objects'] = $this->_data['objects'] - 1;
			$this->_update = true;
        }
        return $this;
	}

    /**
     * Увеличивает счетчик файлов
     *
     * @return Catalogue_Model_Object
     */
    function incrementFilesCount() {
        $this->_data['files'] = $this->_data['files'] + 1;
		$this->_update = true;
        return $this;
    }

    /**
     * Уменьшает счетчик файлов
     *
     * @return Catalogue_Model_Object
     */
    function decrementFilesCount() {
        if ($this->_data['files'] > 0) {
            $this->_data['files'] = $this->_data['files'] - 1;
			$this->_update = true;
        }
        return $this;
    }

    /**
     * Возвращает категорию, к которой привязан объект
     *
     * @return Catalogue_Model_Category
     *
     */
	function getCategory() {
        if ($this->hasCategory()) {
 		    $categoryId = $this->getCategoryId();
            return Catalogue_Model_Category::getInstance($categoryId);
        }
	}

    /**
     * Вызвращает список связанных объектов
     *
     * @param  $where
     * @param  $order
     * @param  $limit
     * @param  $offset
     * @return Catalogue_Model_Object_Iterator
     */
	function getObjects($where = null, $order = null, $limit = null, $offset = null) {
        $offset = self::_getSqlOffset($limit, $offset);

		$storage = $this->_initStorage(Catalogue_Model_Object_Abstract::T_OBJECT);

        return $storage->getObjects($this, $where, $order, $limit, $offset);
    }

    /**
     * Проверяет, привязан ли объект к категории
     *
     * @return bool
     *
     */
    function hasCategory() {
        return $this['category_id'] > 0;
    }

    /**
     * Возвращает ID категории, к которой привязан объект
     *
     * @return int
     *
     */
	function getCategoryId() {
		return (int)$this->_data['category_id'];
	}

    /**
     * Возвращает категории, к которым привязан объект
     *
     * @return Catalogue_Model_Category_Iterator
     *
     */
    function getCategories($filter = null, $order = null, $sortBy = self::SORT_BY_CATEGORY_PRIORITY_FIRST) {

		if (null === $order && null !== $sortBy) {
			if (isInt($sortBy)) {
				switch ($sortBy) {
					case self::SORT_BY_CATEGORY_PRIORITY_FIRST:
						$order = array('i.priority ASC', 'm.sort ASC');
					break;

					case self::SORT_BY_CATEGORY_FIRST:
						$order = array('m.sort ASC', 'i.priority ASC');
					break;

					case self::SORT_BY_CATEGORY_PRIORITY:
						$order = array('i.priority ASC');
					break;

					case self::SORT_BY_CATEGORY:
						$order = array('m.sort ASC');
					break;
				}
			}
		}

		return $this->getStorage()->getCategories($this, $filter, $order);
    }

    /**
     * Добавляет новое значение атрибута (для всех типов атрибутов кроме файлов)
     *
     * @throws Catalogue_Model_Exception
     * @param  $attr
     * @param  $value
     * @return Catalogue_Model_Object
     *
     */
    function addAttrValue($attr, $value) {
        if (!$attr instanceof Catalogue_Model_Attribute) {
            $attr = Catalogue_Model_Attribute::getInstance((int)$attr);
        }

        if ($attr->isFile()) {
            throw new Workset_Model_Exception("For add attribute value of type FILE you cannot use this method! Use addFile(attr, fileinfo) instead");
        }

        $this->_attrs[] = array(
            'attr' => $attr,
            'value' => $value,
        );

        return $this;
    }

    /**
     * Добавляет список значений атрибутов
     *
     * @param  $values
     * @param  $attrs
     * @return Catalogue_Model_Object
     */
    function addAttrValues(array $values, array $attrs = null) {
        if (!$attrs) {
            foreach ($values as $spec) {
                if (!isset($spec['attr']) || !isset($spec['value'])) {
                    continue;
                }
                $this->addAttrValue($spec['attr'], $spec['value']);
            }
        } else {
            foreach ($attrs as $attr) {
                if (isset($values[$attr->getId()])) {
                    $this->addAttrValue($attr, $values[$attr->getId()]);
                }
            }
        }

        return $this;
    }

    /**
     * Удаляет значение атрибута
     *
     * @param  $attr
     * @param  $value
     * @return Catalogue_Model_Object
     */
	function deleteAttrValue($attr, $value = null) {
		if (!$attr instanceof Catalogue_Model_Attribute) {
            $attr = Catalogue_Model_Attribute::getInstance((int)$attr);
        }

        $this->_attrsToDelete[] = array(
            'attr' => $attr,
            'value' => $value,
        );

        return $this;
	}

    /**
     * Обновляет значение атрибута по его ID
     * Возвращает ID обновленного значения
     *
     * @param  $id
     * @param  $value
     * @return string
     */
	function updateAttrValueById($id, $value) {
		$filter = self::getPlugin('filter', 'ValueIndex');
		$valueId = $filter->filter($value);

		$storage = $this->_initStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
		return $storage->updateAttrValueById($id, $valueId, $value);
	}

	/**
	 * @param  $attr
	 * @param  $value
	 * @return void
	 *
	 */
    protected function _setAttrValue(Catalogue_Model_Attribute $attr, $value, $valueId = null) {
        $storage = $this->_initStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
        $success = false;

		$attrId = $attr->getId();
		$values = $this->getAttrValuesSimple($attrId);

        $flags = $attr->getValueFlagList();
        $flags = self::flags2Int($flags);
        $file = $attr->isFile();
        $updatedId = array();

        if ($file) {
            if (null === $value) {
                return true;
            }
            $valueId = 0;
        } 

        if (null === $valueId) {
            $filter = self::getPlugin('filter', 'ValueIndex');
            $valueId = $filter->filter($value);
        }

		//обновляем существующее значение атрибута
        if (!$attr->isMultiple() && !empty($values)) {

            $success = $storage->updateAttrValue($this, $attrId, $valueId, $value, $flags);

			//удаляем старый файл
            if ($success && $file) {
                $this->_dropFiles(array($values['value']));
            }
        }
		//добавляем новое значение в список значений или обновляем существующее
		//(для множеств.атрибута)
		elseif($attr->isMultiple()) {
            $value = (array)$value;

            $returnId = array();
			$updatedFiles = array();

            foreach ($value as $id => $valueItem) {

                if (null === $valueItem) {
                    if (isset($values['value'][$id])) {
                        unset($values['value'][$id]);
                    }
                    $success = true;
                    continue;
                }

                $valId = $valueId !== 0 && isset($valueId[$id]) ? $valueId[$id] : 0;

                if (isset($values['value'][$id])) {
                    $success = $storage->updateAttrValueById($id, $valId, $valueItem, $flags);
                    if (!$success) {
                        return;
                    }

					if ($file) {
						$updatedFiles[] = $values['value'][$id];
					}

                    $returnId[] = $success;
                    $updatedId[] = $id;

                } else {
                    $success = $storage->addAttrValue($this, $attrId, $valId, $valueItem, $flags);
                    if (!$success) {
                        return;
                    }

                    $returnId[] = $success;

                    if ($file) {
                        $this->incrementFilesCount();
                    }
                }

                $success = false;
            }

			if ($updatedFiles) {
				$this->_dropFiles($updatedFiles);
			}

            //удаление старых значений
            if (!empty($values['value'])) {
                $delList = array_diff(array_keys($values['value']), $updatedId);
                if ($delList) {
                   $this->_deleteAttrValue($attr, $delList);
                }
            }

            if ($returnId) {
                $success = $returnId;
            }
        }
		//добавляем новое значение
		else {
			$success = $storage->addAttrValue($this, $attrId, $valueId, $value, $flags);

            if ($file) {
                $this->incrementFilesCount();
            }
		}
        return $success;
    }

	/**
	 * @param  $attr
	 * @param  $value
	 * @return void
	 *
	 */
    protected function _setAttrValueEnum(Catalogue_Model_Attribute $attr, $value) {
        $storage = $this->_initStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
        $attrId = $attr->getId();
		$values = $this->getAttrValuesEnum($attrId);

        $flags = $attr->getValueFlagList();
        $flags = self::flags2Int($flags);

		if ($attr->isMultiple()) {
            if (is_array($value)) {
                $value = array_unique($value);
            } else {
                $value = (array)$value;
            }

            $value = array_filter($value);

            $savedValues = $values ? array_values($values['id']) : array();

            $deleteValues = array_diff($savedValues, $value);
            $addValues = array_diff($value, $savedValues);

            if ($deleteValues) {
                $this->_deleteAttrValue($attr, $deleteValues);
            }

			if (empty($addValues)) {
				return true;
			}

            $valId = array();

            foreach ($addValues as $val) {
                $valId[] = $storage->addAttrValueEnum($this, $attrId, $val, $flags);
            }

			if (in_array(false, $valId)) {
				return;
			}
			
            return $valId;

		}

        $value = (int)$value;

        //удаление старого значения
        if ($value <= 0) {
            if ($values) {
                $this->_deleteAttrValue($attr);
            }
            return true;
        }

        //обновление/добавление значения
        if ($values) {
			return $storage->updateAttrValueEnum($this, $attrId, $values['value_id'], $value, $flags);
		} else {
			return $storage->addAttrValueEnum($this, $attrId, $value, $flags);
		}
    }

    /**
     * @throws Catalogue_Model_Exception
     * @param  $attr
     * @return Catalogue_Model_Object
     */
    protected function _deleteAttrValue($attr, $values = null) {
        if (!$attr instanceof Catalogue_Model_Attribute) {
            $attr = Catalogue_Model_Attribute::getInstance((int)$attr);
        }

        if ($attr->isFile()) {
            return $this->_deleteAttrValueFile($attr, $values);
        }

        $storage = $this->_initStorage(Catalogue_Model_Object_Abstract::T_OBJECT);

		if ($attr->isEnum()) {
            return $storage->deleteAttrValuesEnum($this, $attr->getId(), $values);
        } else {
			return $storage->deleteAttrValues($this, $attr->getId(), $values);
		}
    }

    protected function _deleteAttrValueFile(Catalogue_Model_Attribute $attr, $values = null) {
        if (!$attr->isFile()) {
            throw new Workset_Model_Exception("Attribute is not file");
        }

        $value = $this->getAttrValuesSimple($attr->getId());

        //в $values ID удаляемых значений
        if (null !== $values && $attr->isMultiple()) {
            $value = array_intersect_key($value['value'], array_flip((array)$values));
        } else {
            $value = (array)$value;
        }

        $this->_dropFiles($value);

        foreach ($value as $file) {
            $this->decrementFilesCount();
        }

        $storage = $this->_initStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
        return $storage->deleteAttrValues($this, $attr->getId(), $values);
    }

    /**
     * Возвращает значения атрибутов объекта
     *
     * @param  string $key
     * @return array
     *
     */
    function getAttrValues($key = null) {

        $this->_getAttrValuesEnum();
        $this->_getAttrValuesSimple();

        if (isInt($key)) {
            if (isset($this->_attrValues[$key])) {
                return $this->_attrValues[$key];
            } elseif (isset($this->_attrValuesEnum[$key])) {
                return $this->_attrValuesEnum[$key];
            } else {
                return null;
            }
        }

        return $this->_attrValues + $this->_attrValuesEnum;
    }

    /**
     * Возвращает список ID значений атрибутов (для типа список)
     *
     * @param  $attrs
     * @param  $key
     * @return array
     *
     */
    function getAttrValuesEnum($key = null) {
        $this->_getAttrValuesEnum();

        if ($key !== null) {
			if (is_array($key)) {
                return array_intersect_key($this->_attrValuesEnum, array_flip($key));
            } elseif (isset($this->_attrValuesEnum[$key])) {
                return $this->_attrValuesEnum[$key];
            } else {
               	return null;
            }
        }

        return $this->_attrValuesEnum;
    }

    /**
     * Возвращает список простых значений атрибутов
     *
     * @throws Catalogue_Model_Exception
     * @param  $key
     * @return array
     */
    function getAttrValuesSimple($key = null) {
        $this->_getAttrValuesSimple();

        if (null !== $key) {
            if (is_array($key)) {
                return array_intersect_key($this->_attrValues, array_flip($key));
            } elseif (isset($this->_attrValues[$key])) {
                return $this->_attrValues[$key];
            } else {
                return null;
            }
        }

        return $this->_attrValues;
    }

    protected function _getAttrValuesSimple() {
        if (null === $this->_attrValues) {
            $storage = $this->_initStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
            $this->_attrValues = $this->_normalizeAttrValues($storage->getAttrValues($this));
        }

        return $this->_attrValues;
    }

    protected function _getAttrValuesEnum() {
        if (null === $this->_attrValuesEnum) {
            $storage = $this->_initStorage(Catalogue_Model_Object_Abstract::T_OBJECT);
            $this->_attrValuesEnum = $this->_normalizeAttrValues($storage->getAttrValuesEnum($this));
        }

        return $this->_attrValuesEnum;
    }

    /**
     * Добавляет связь объекта с категорией
     *
     * @param  $category
     * @return Catalogue_Model_Object
     */
	function addCategory($category) {
		if (!$category instanceof Catalogue_Model_Category) {
			$category = Catalogue_Model_Category::getInstance($category);
		}

        if ($category) {
			$id = $category->getId();
			if ($id > 0 && !isset($this->_categories[$id])) {
				$this->_categories[$id] = $category;
			}
        }

		return $this;
	}

    /**
     * Добавляет список категорий (для связи)
     *
     * @param  $categories
     * @return Catalogue_Model_Object
     */
    function addCategories($categories) {
        if (is_array($categories) || $categories instanceof Iterator) {
            foreach ($categories as $category) {
                $this->addCategory($category);
            }
        }

        return $this;
    }

    /**
     * Удаляет связь с категорией
     *
     * @param  $category
     * @return Catalogue_Model_Object
     */
    function deleteCategory($category) {
        if (!$category instanceof Catalogue_Model_Category) {
			$category = Catalogue_Model_Category::getInstance($category);
		}

        if ($category) {
            $this->_categoriesToDelete[] = $category;
        }

        return $this;
    }

    /**
     * Добавляет связь с объектом
     *
     * @param  $object
     * @return Catalogue_Model_Object
     *
     */
    function addObject($object) {
		if (!$object instanceof Catalogue_Model_Object) {
			$object = Catalogue_Model_Object::getInstance($object);
		}

        if ($object) {
            $this->_objects[] = $object;
        }

		return $this;
    }

    /**
     * Добавляет список объектов (для связи)
     *
     * @param  $objects
     * @return Catalogue_Model_Object
     */
    function addObjects($objects) {
        if (is_array($objects) || $objects instanceof Iterator) {
            foreach ($objects as $object) {
                $this->addObject($object);
            }
        }

        return $this;
    }

    /**
     * Удаляет связь с объектом
     *
     * @param  $object
     * @return Catalogue_Model_Object
     */
    function deleteObject($object) {
        if (!$object instanceof Catalogue_Model_Object) {
			$object = Catalogue_Model_Object::getInstance($object);
	    }

        if ($object) {
            $this->_objectsToDelete[] = $object;
        }

        return $this;
    }

    /**
     * Удаляет связь с объектами
     *
     * @param  $objects
     * @return Catalogue_Model_Object
     */
	function deleteObjects($objects) {
        if (is_array($objects) || $objects instanceof Iterator) {
            foreach ($objects as $object) {
                $this->deleteObject($object);
            }
        }

        return $this;
	}

    /**
     * Добавляет значение атрибута типа ФАЙЛ
     *
     * @throws Catalogue_Model_Exception
     * @param  $attr
     * @param  $info
     * @return Catalogue_Model_Object
     */
	function addFile($attr, $info) {
		if (!$attr instanceof Catalogue_Model_Attribute) {
            $attr = Catalogue_Model_Attribute::getInstance((int)$attr);
        }

		if (!$attr->isFile()) {
			throw new Workset_Model_Exception('Invalid attribute type');
		}

		if ($attr->isMultiple()) {
			foreach ($info as $key => $fileinfo) {
				if ($fileinfo instanceof Workset_File_Info) {
					$fileinfo = $fileinfo->toArray(array('ctime', 'mimetype', 'name', 'shortpath', 'id'));
				}

                if ($fileinfo) {
                    $info[$key] = serialize($fileinfo);
                }
			}
		} else {
            if ($info instanceof Workset_File_Info) {
                $info = $info->toArray(array('ctime', 'mimetype', 'name', 'shortpath', 'id'));
            }

            if (!$info) {
                return $this;
            }

            $info = serialize($info);
		}

        $this->_attrs[] = array(
            'attr' => $attr,
            'value' => $info,
        );

		return $this;
	}

    /**
     * Добавляет значения атрибутов типа ФАЙЛ
     *
     * @param  array|Iterator $info
     * @return Catalogue_Model_Object
     */
	function addFiles($info) {
		if (is_array($info) || $info instanceof Iterator) {
            foreach ($info as $attr => $fileinfo) {
                $this->addFile($attr, $fileinfo);
            }
        }

        return $this;
	}

    /**
     * группирует массив по ключу
     *
     * @param  $groupKey
     * @param  $values
     * @return array
     *
     */
    protected function _groupArray($groupKey, $values) {
        $result = array();
        foreach ($values as $key => $data) {
           $result[$data[$groupKey]] = $data;
        }
        return $result;
    }

    /**
     * Группирует массив значений атрибутов
     *
     * Для множественного значения атрибута структура саммива следующая:
     *
     * array(
     *      'id' => array(
     *          ID_value
     *      ),
     *      'attr_id' => ID_attr,
     *      'object_id' => ID_object,
     *      'multiple' => true,
     *      'value_id' => array(
     *          ID_value => value_id
     *      ),
     *      'value' => array(
     *          ID_value => value
     *      )
     * )
     *
     * Для одиночного значения атрибута:
     *
     * array(
     *      'id' => ID_value,
     *      'attr_id' => ID_attr,
     *      'object_id' => ID_object,
     *      'multiple' => false,
     *      'value_id' => value_id,
     *      'value' => value
     * )
     *
     * @param  $values
     * @return array
     *
     */
	protected function _normalizeAttrValues($values) {
		$result = array();

		foreach ($values as $key => $fields) {
            $attrId = $fields['attr_id'];

            if (!self::isFlag(Catalogue_Model_Attribute::FLAG_VALUE_ENUM, $fields['flag'])) {
                $keyId = 'id';
                $keyValue = 'value';
                $keyValueId = 'value_id';

                $fields['enum'] = false;
            } else {
                $keyId = 'value_id';
                $keyValue = 'value_id';
                $keyValueId = 'value_id';

                $fields['enum'] = true;
            }

            if (self::isFlag(Catalogue_Model_Attribute::FLAG_VALUE_FILE, $fields['flag'])) {
                $fields['value'] = unserialize($fields['value']);
                $fields['type'] = Catalogue_Model_Attribute::T_FILE;
            } else {
                $fields['type'] = false;
            }

            if (self::isFlag(Catalogue_Model_Attribute::FLAG_VALUE_MULTIPLE, $fields['flag'])) {
                $multi = true;
            } else {
                $multi = false;
            }

            if (!isset($result[$attrId])) {
                $result[$attrId] = array(
                    'attr_id' => $attrId,
                    'object_id' => $fields['object_id'],
                    'multiple' => $multi,
                    'type' => $fields['type']
                );
            }

            $row = & $result[$attrId];

            if ($multi) {
                $row['value'][$fields[$keyId]] = $fields[$keyValue];
                $row['value_id'][$fields[$keyId]] = $fields[$keyValueId];
                $row['id'][] = (int)$fields[$keyId];
            } else {
                $row['value']= $fields[$keyValue];
                $row['value_id'] = $fields[$keyValueId];
                $row['id'] = (int)$fields[$keyId];
            }
		}

		return $result;
	}

    /**
     * Возвращает список атрибутов из всех категорий к которым привязан объект
     *
     * @deprecated
     * 
     * @param  $categoryFilter
     * @param  $categoryOrder
     *
     * @return array
     *
     */
    function getAttributes($categoryFilter = null, $categoryOrder = null, $groupByCategory = false) {
        return Catalogue_Model_Category::getCategoryAttributes(
            $this->getCategories($categoryFilter, $categoryOrder), $groupByCategory
        );
    }

    /**
     * Возвращает поля объекта в формате для отображения на сайте
     *
     * @param  $key
     * @return array
     *
     */
    function getDisplayData($key = null) {
        $data = $this->getParam('display', $key);
        if (null !== $data) {
            return $data;
        }

        $data = self::getOutputData($this->toArray(), $key);
        $this->setParam('display', $data);

        return $data;
    }

    /**
     * Удаляет файлы принадлежащие объекту
     *
     * @param  $values
     * @return bool
     */
    function dropFiles($values = null) {
        if ($this->_data['files'] < 1) {
            return true;
        }

        if (null !== $values) {
            $values = (array)$values;
        }

        $values = $this->getAttrValuesSimple($values);

        if (!$values) {
            return true;
        }

        $files = array();

        foreach ($values as $fields) {
            if ($fields['type'] == Catalogue_Model_Attribute::T_FILE) {
                if ($fields['multiple']) {
                    foreach ($fields['value'] as $val) {
                       $files[] = $val;
                    }
                } else {
                    $files[] = $fields['value'];
                }
            }
        }

        if (!$files) {
            return true;

        }
        //обнуление счетчика файлов
        elseif ($this['files'] > 0) {

            $this['files'] -= count($files);
            if ($this['files'] < 0) {
                $this['files'] = 0;
            }

        }

        return parent::dropFiles($files);
    }

    /**
     * Чистит временные данные
     *
     * @return void
     */
    protected function _clean() {
        $this->_categories = array();
        $this->_categoriesToDelete = array();
		$this->_objects = array();
        $this->_objectsToDelete = array();
        $this->_attrs = array();
		$this->_attrsToDelete = array();
        $this->_attrValues = null;
        $this->_attrValuesEnum = null;
    }

    /**
     * Преобразует поля объекта во внутренний формат (для хранения в БД)
     *
     * @static
     * @param
     * @return array
     *
     */
    static function getInputData($data, $key = null) {
        if (null !== $key) {
            $inputData = array($key => $data[$key]);
        } elseif ($data instanceof Workset_Model_Row_Interface) {
            $inputData = $data->toArray();
        } else {
            $inputData = $data;
        }

        foreach ($inputData as $key => $value) {
            switch ($key) {
                case 'created':
                case 'pub_date':
                case 'pub_period':
                    $inputData[$key] = self::inputDateFormat($value);
                break;
            }
        }

        return $inputData;
    }

    /**
     * Преобразует поля для отображения на сайте
     *
     * @static
     * @param  $data
     * @param  $key
     * @return array
     *
     */
    static function getOutputData($data, $key = null) {
        if (null !== $key) {
            $outputData = array($key => $data[$key]);
        } elseif ($data instanceof Workset_Model_Row_Interface) {
            $outputData = $data->toArray();
        } else {
            $outputData = $data;
        }

        foreach ($outputData as $key => $value) {
            switch ($key) {
                case 'created':
                case 'pub_date':
                case 'pub_period':
                    $outputData[$key] = self::outputDateFormat($value, null, self::$_dateFormat);
                break;
            }
        }

        return $outputData;
    }

    /**
     * Возвращает текущее значение атрибута для вывода в html-форме
     *
     * @static
     * @param  $attr
     * @param  $source
     * @return array|int|string
     *
     */
    static function getFormAttrValue($attr, array &$source) {
        if (!$attr instanceof Catalogue_Model_Attribute) {
            $attr = Catalogue_Model_Attribute::getInstance((int)$attr);
        }

        if ($attr) {
            if (isset($source[$attr->getId()])) {

                $value = $source[$attr->getId()];

                if (is_array($value)) {
                    return $value['value'];
                }

                return $value;
            }
        }
    }

    /**
     * Возвращает массив значений атрибутов в виде структуры полей
     * переданной через web-форму
     *
     * @static
     * @param  $attrs
     * @param  $source
     * @param  $prefix
     * @return array
     *
     */
    static function getFormAttrValues(array $attrs, array $source, $prefix = null) {
        $result = array();

        foreach ($attrs as $attr) {
            $value = self::getFormAttrValue($attr, $source);
            $id = $attr->getId();
            if ($attr->isMultiple()) {
                foreach ((array)$value as $key => $valueItem) {
                    $value[$id.Catalogue_Form_Object::ATTR_VALUE_SPR.$key] = $valueItem;
                    unset($value[$key]);
                }
            }
            $result[Catalogue_Form_Object::ATTR_PREFIX.$id] = $value;
        }

        if (is_string($prefix)) {
           $result = array($prefix => $result);
        }

        return $result;
    }

    /**
     * Возвращает объекты из поискового индекса
     *
     * @static
     * @param  array $attrs - массив значений атрибутов
     * @param  $matchText - поиск по тексту
     * @return Catalogue_Model_Object_Iterator
     *
     *
     */
	static function searchObjects($attrs, $matchText = null) { }

    /**
     * Возвращает список объектов из кеша
     *
     * @static
     * @param  $listId
     * @param  $cacheAdapter - тип кеша
     * @return Catalogue_Model_Object_Iterator
     *
     */
	static function getFromCache($listId, $cacheAdapter = null) { }

    /**
     * Возвращает объект из кеша
     *
     * @static
     * @param  $id
     * @param  $cacheAdapter - тип кеша
     * @return void
     *
     */
	static function getOneFromCache($id, $cacheAdapter = null) { }

    /**
     * Удаляет файлы
     *
     * @param  $files
     * @return void
     *
     */
    protected function _dropFiles(array $files) {
        return parent::dropFiles($files);
    }

}

?>